package com.gitee.wsl.mathematics.geometry.d2.line

import com.gitee.wsl.mathematics.coordinate.d2.Coordinate2
import com.gitee.wsl.mathematics.vector.ext.cross
import com.gitee.wsl.mathematics.geometry.api.LineShape
import com.gitee.wsl.mathematics.geometry.d2.Shape2d
import com.gitee.wsl.mathematics.geometry.d2.base.YPolarity
import com.gitee.wsl.mathematics.geometry.d2.contour.segment.perpendicular
import com.gitee.wsl.mathematics.geometry.d2.line.ext.projectedPoint
import com.gitee.wsl.mathematics.vector.ext.between
import com.gitee.wsl.mathematics.vector.ext.distanceTo
import com.gitee.wsl.mathematics.vector.ext.dot
import com.gitee.wsl.mathematics.vector.ext.squaredDistanceTo
import com.gitee.wsl.mathematics.vector.ext.times
import com.gitee.wsl.unit.angle.AngleUnit

interface LineShape2d<T:Number,V: Coordinate2<T, V>>: LineShape<T, V>, Shape2d<T, V> {

    /** Direction of the line segment */
    val direction get() = (b - a)

    /** The normal of the line segment, a unit vector. */
    val normal get() = (b - a).normalized.perpendicular(YPolarity.CW_NEGATIVE_Y)

    override val perimeter: T get() = length

    override val angle: AngleUnit
        get() = AngleUnit.between(a,b)
    override val length: T
        get() = a.distanceTo(b)
    override val lengthSquared: T
        get() = a.squaredDistanceTo(b)

    val start get() = a
    val end get() = b

    override val x0: T get() = a.x
    override val y0: T get() = a.y

    override val x1: T get() = b.x
    override val y1: T get() = b.y

    override val dx: T get() = x1 - x0
    override val dy: T get() = y1 - y0

    val min get() = createPoint2(minX, minY)
    override val minX: T get() = min(a.x, b.x)
    override val minY: T get() = min(a.y, b.y)

    val max get() = createPoint2(maxX, maxY)
    override val maxX: T get() = max(a.x, b.x)
    override val maxY: T get() = max(a.y, b.y)

    override fun directionVector() = createPoint2(dx, dy)

    override fun create(a:V, b:V):LineShape2d<T,V>

    fun containsX(x: T): Boolean = x.inRange( x0,x1) || x.inRange(x1,x0) || isFuzzyEqual(x, x0) || isFuzzyEqual(x, x1)

    fun containsY(y: T): Boolean = y.inRange( y0,y1) || y.inRange(y1,y0) || isFuzzyEqual(y, y0) || isFuzzyEqual(y, y1)

    override fun containsPoint(p: Coordinate2<T, *>): Boolean = containsX(p.x) && containsY(p.y)

    fun normalVectorAt(p: Coordinate2<T, *>): V {
        val projected = projectedPoint(p)
        return (b - a).perpendicular.normalized * sign((projected cross p))
    }

    fun nearestPointInLine(p: Coordinate2<T, *>): T {
        val bSa = b - a
        val pSa = p * a
        return (bSa dot pSa) / bSa.squaredLength
    }

    fun side(v:  Coordinate2<T, *>): T {
        return sign((b.x - a.x) * (v.y - a.y) - (b.y - a.y) * (v.x - a.x))
    }

}



